(function ($, window, document) {
    var STATE_BEFORECHANGEOFFSET = 'beforeChangeOffset';
    var STATE_AFTERCHANGEOFFSET = 'afterChangeOffset';

    var EVENT_PULLSTART = 'pullstart';
    var EVENT_PULLING = 'pulling';
    var EVENT_BEFORECHANGEOFFSET = STATE_BEFORECHANGEOFFSET;
    var EVENT_AFTERCHANGEOFFSET = STATE_AFTERCHANGEOFFSET;
    var EVENT_DRAGENDAFTERCHANGEOFFSET = 'dragEndAfterChangeOffset';

    var CLASS_TRANSITIONING = $.className('transitioning');
    var CLASS_PULL_TOP_TIPS = $.className('pull-top-tips');
    var CLASS_PULL_BOTTOM_TIPS = $.className('pull-bottom-tips');
    var CLASS_PULL_LOADING = $.className('pull-loading');
    var CLASS_SCROLL = $.className('scroll');

    var CLASS_PULL_TOP_ARROW = $.className('pull-loading') + ' ' + $.className('icon') + ' ' + $.className('icon-pulldown');
    var CLASS_PULL_TOP_ARROW_REVERSE = CLASS_PULL_TOP_ARROW + ' ' + $.className('reverse');
    var CLASS_PULL_TOP_SPINNER = $.className('pull-loading') + ' ' + $.className('spinner');
    var CLASS_HIDDEN = $.className('hidden');

    var SELECTOR_PULL_LOADING = '.' + CLASS_PULL_LOADING;
    $.PullToRefresh = $.Class.extend({
        init: function (element, options) {
            this.element = element;
            this.options = $.extend(true, {
                down: {
                    height: 75,
                    callback: false,
                },
                up: {
                    auto: false,
                    offset: 100, //距离底部高度(到达该高度即触发)
                    show: true,
                    contentinit: '上拉显示更多',
                    contentdown: '上拉显示更多',
                    contentrefresh: '正在加载...',
                    contentnomore: '没有更多数据了',
                    callback: false
                },
                preventDefaultException: {
                    tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/
                }
            }, options);
            this.stopped = this.isNeedRefresh = this.isDragging = false;
            this.state = STATE_BEFORECHANGEOFFSET;
            this.isInScroll = this.element.classList.contains(CLASS_SCROLL);
            this.initPullUpTips();

            this.initEvent();
        },
        _preventDefaultException: function (el, exceptions) {
            for (var i in exceptions) {
                if (exceptions[i].test(el[i])) {
                    return true;
                }
            }
            return false;
        },
        initEvent: function () {
            if ($.isFunction(this.options.down.callback)) {
                this.element.addEventListener($.EVENT_START, this);
                this.element.addEventListener('drag', this);
                this.element.addEventListener('dragend', this);
            }
            if (this.pullUpTips) {
                this.element.addEventListener('dragup', this);
                if (this.isInScroll) {
                    this.element.addEventListener('scrollbottom', this);
                } else {
                    window.addEventListener('scroll', this);
                }
            }
        },
        handleEvent: function (e) {
            switch (e.type) {
                case $.EVENT_START:
                    this.isInScroll && this._canPullDown() && e.target && !this._preventDefaultException(e.target, this.options.preventDefaultException) && e.preventDefault();
                    break;
                case 'drag':
                    this._drag(e);
                    break;
                case 'dragend':
                    this._dragend(e);
                    break;
                case 'webkitTransitionEnd':
                    this._transitionEnd(e);
                    break;
                case 'dragup':
                case 'scroll':
                    this._dragup(e);
                    break;
                case 'scrollbottom':
                    if (e.target === this.element) {
                        this.pullUpLoading(e);
                    }
                    break;
            }
        },
        initPullDownTips: function () {
            var self = this;
            if ($.isFunction(self.options.down.callback)) {
                self.pullDownTips = (function () {
                    var element = document.querySelector('.' + CLASS_PULL_TOP_TIPS);
                    if (element) {
                        element.parentNode.removeChild(element);
                    }
                    if (!element) {
                        element = document.createElement('div');
                        element.classList.add(CLASS_PULL_TOP_TIPS);
                        element.innerHTML = '<div class="mui-pull-top-wrapper"><span class="mui-pull-loading mui-icon mui-icon-pulldown"></span></div>';
                        element.addEventListener('webkitTransitionEnd', self);
                    }
                    self.pullDownTipsIcon = element.querySelector(SELECTOR_PULL_LOADING);
                    document.body.appendChild(element);
                    return element;
                }());
            }
        },
        initPullUpTips: function () {
            var self = this;
            if ($.isFunction(self.options.up.callback)) {
                self.pullUpTips = (function () {
                    var element = self.element.querySelector('.' + CLASS_PULL_BOTTOM_TIPS);
                    if (!element) {
                        element = document.createElement('div');
                        element.classList.add(CLASS_PULL_BOTTOM_TIPS);
                        if (!self.options.up.show) {
                            element.classList.add(CLASS_HIDDEN);
                        }
                        element.innerHTML = '<div class="mui-pull-bottom-wrapper"><span class="mui-pull-loading">' + self.options.up.contentinit + '</span></div>';
                        self.element.appendChild(element);
                    }
                    self.pullUpTipsIcon = element.querySelector(SELECTOR_PULL_LOADING);
                    return element;
                }());
            }
        },
        _transitionEnd: function (e) {
            if (e.target === this.pullDownTips && this.removing) {
                this.removePullDownTips();
            }
        },
        _dragup: function (e) {
            var self = this;
            if (self.loading) {
                return;
            }
            if (e && e.detail && $.gestures.session.drag) {
                self.isDraggingUp = true;
            } else {
                if (!self.isDraggingUp) { //scroll event
                    return;
                }
            }
            if (!self.isDragging) {
                if (self._canPullUp()) {
                    self.pullUpLoading(e);
                }
            }
        },
        _canPullUp: function () {
            if (this.removing) {
                return false;
            }
            if (this.isInScroll) {
                var scrollId = this.element.parentNode.getAttribute('data-scroll');
                if (scrollId) {
                    var scrollApi = $.data[scrollId];
                    return scrollApi.y === scrollApi.maxScrollY;
                }
            }
            return window.pageYOffset + window.innerHeight + this.options.up.offset >= document.documentElement.scrollHeight;
        },
        _canPullDown: function () {
            if (this.removing) {
                return false;
            }
            if (this.isInScroll) {
                var scrollId = this.element.parentNode.getAttribute('data-scroll');
                if (scrollId) {
                    var scrollApi = $.data[scrollId];
                    return scrollApi.y === 0;
                }
            }
            return document.body.scrollTop === 0;
        },
        _drag: function (e) {
            if (this.loading || this.stopped) {
                e.stopPropagation();
                e.detail.gesture.preventDefault();
                return;
            }
            var detail = e.detail;
            if (!this.isDragging) {
                if (detail.direction === 'down' && this._canPullDown()) {
                    if (document.querySelector('.' + CLASS_PULL_TOP_TIPS)) {
                        e.stopPropagation();
                        e.detail.gesture.preventDefault();
                        return;
                    }
                    this.isDragging = true;
                    this.removing = false;
                    this.startDeltaY = detail.deltaY;
                    $.gestures.session.lockDirection = true; //锁定方向
                    $.gestures.session.startDirection = detail.direction;
                    this._pullStart(this.startDeltaY);
                }
            }
            if (this.isDragging) {
                e.stopPropagation();
                e.detail.gesture.preventDefault();
                var deltaY = detail.deltaY - this.startDeltaY;
                deltaY = Math.min(deltaY, 1.5 * this.options.down.height);
                this.deltaY = deltaY;
                this._pulling(deltaY);
                var state = deltaY > this.options.down.height ? STATE_AFTERCHANGEOFFSET : STATE_BEFORECHANGEOFFSET;
                if (this.state !== state) {
                    this.state = state;
                    if (this.state === STATE_AFTERCHANGEOFFSET) {
                        this.removing = false;
                        this.isNeedRefresh = true;
                    } else {
                        this.removing = true;
                        this.isNeedRefresh = false;
                    }
                    this['_' + state](deltaY);
                }
                if ($.os.ios && parseFloat($.os.version) >= 8) {
                    var clientY = detail.gesture.touches[0].clientY;
                    if ((clientY + 10) > window.innerHeight || clientY < 10) {
                        this._dragend(e);
                        return;
                    }
                }
            }
        },
        _dragend: function (e) {
            var self = this;
            if (self.isDragging) {
                self.isDragging = false;
                self._dragEndAfterChangeOffset(self.isNeedRefresh);
            }
            if (self.isPullingUp) {
                if (self.pullingUpTimeout) {
                    clearTimeout(self.pullingUpTimeout);
                }
                self.pullingUpTimeout = setTimeout(function () {
                    self.isPullingUp = false;
                }, 1000);
            }
        },
        _pullStart: function (startDeltaY) {
            this.pullStart(startDeltaY);
            $.trigger(this.element, EVENT_PULLSTART, {
                api: this,
                startDeltaY: startDeltaY
            });
        },
        _pulling: function (deltaY) {
            this.pulling(deltaY);
            $.trigger(this.element, EVENT_PULLING, {
                api: this,
                deltaY: deltaY
            });
        },
        _beforeChangeOffset: function (deltaY) {
            this.beforeChangeOffset(deltaY);
            $.trigger(this.element, EVENT_BEFORECHANGEOFFSET, {
                api: this,
                deltaY: deltaY
            });
        },
        _afterChangeOffset: function (deltaY) {
            this.afterChangeOffset(deltaY);
            $.trigger(this.element, EVENT_AFTERCHANGEOFFSET, {
                api: this,
                deltaY: deltaY
            });
        },
        _dragEndAfterChangeOffset: function (isNeedRefresh) {
            this.dragEndAfterChangeOffset(isNeedRefresh);
            $.trigger(this.element, EVENT_DRAGENDAFTERCHANGEOFFSET, {
                api: this,
                isNeedRefresh: isNeedRefresh
            });
        },
        removePullDownTips: function () {
            if (this.pullDownTips) {
                try {
                    this.pullDownTips.parentNode && this.pullDownTips.parentNode.removeChild(this.pullDownTips);
                    this.pullDownTips = null;
                    this.removing = false;
                } catch (e) {
                }
            }
        },
        pullStart: function (startDeltaY) {
            this.initPullDownTips(startDeltaY);
        },
        pulling: function (deltaY) {
            this.pullDownTips.style.webkitTransform = 'translate3d(0,' + deltaY + 'px,0)';
        },
        beforeChangeOffset: function (deltaY) {
            this.pullDownTipsIcon.className = CLASS_PULL_TOP_ARROW;
        },
        afterChangeOffset: function (deltaY) {
            this.pullDownTipsIcon.className = CLASS_PULL_TOP_ARROW_REVERSE;
        },
        dragEndAfterChangeOffset: function (isNeedRefresh) {
            if (isNeedRefresh) {
                this.pullDownTipsIcon.className = CLASS_PULL_TOP_SPINNER;
                this.pullDownLoading();
            } else {
                this.pullDownTipsIcon.className = CLASS_PULL_TOP_ARROW;
                this.endPullDownToRefresh();
            }
        },
        pullDownLoading: function () {
            if (this.loading) {
                return;
            }
            if (!this.pullDownTips) {
                this.initPullDownTips();
                this.dragEndAfterChangeOffset(true);
                return;
            }
            this.loading = true;
            this.pullDownTips.classList.add(CLASS_TRANSITIONING);
            this.pullDownTips.style.webkitTransform = 'translate3d(0,' + this.options.down.height + 'px,0)';
            this.options.down.callback.apply(this);
        },
        pullUpLoading: function (e) {
            if (this.loading || this.finished) {
                return;
            }
            this.loading = true;
            this.isDraggingUp = false;
            this.pullUpTips.classList.remove(CLASS_HIDDEN);
            e && e.detail && e.detail.gesture && e.detail.gesture.preventDefault();
            this.pullUpTipsIcon.innerHTML = this.options.up.contentrefresh;
            this.options.up.callback.apply(this);
        },
        endPullDownToRefresh: function () {
            this.loading = false;
            this.pullUpTips && this.pullUpTips.classList.remove(CLASS_HIDDEN);
            this.pullDownTips.classList.add(CLASS_TRANSITIONING);
            this.pullDownTips.style.webkitTransform = 'translate3d(0,0,0)';
            if (this.deltaY <= 0) {
                this.removePullDownTips();
            } else {
                this.removing = true;
            }
            if (this.isInScroll) {
                $(this.element.parentNode).scroll().refresh();
            }
        },
        endPullUpToRefresh: function (finished) {
            if (finished) {
                this.finished = true;
                this.pullUpTipsIcon.innerHTML = this.options.up.contentnomore;
                this.element.removeEventListener('dragup', this);
                window.removeEventListener('scroll', this);
            } else {
                this.pullUpTipsIcon.innerHTML = this.options.up.contentdown;
            }
            this.loading = false;
            if (this.isInScroll) {
                $(this.element.parentNode).scroll().refresh();
            }
        },
        setStopped: function (stopped) {
            if (stopped != this.stopped) {
                this.stopped = stopped;
                this.pullUpTips && this.pullUpTips.classList[stopped ? 'add' : 'remove'](CLASS_HIDDEN);
            }
        },
        refresh: function (isReset) {
            if (isReset && this.finished && this.pullUpTipsIcon) {
                this.pullUpTipsIcon.innerHTML = this.options.up.contentdown;
                this.element.addEventListener('dragup', this);
                window.addEventListener('scroll', this);
                this.finished = false;
            }
        }
    });
    $.fn.pullToRefresh = function (options) {
        var pullRefreshApis = [];
        options = options || {};
        this.each(function () {
            var self = this;
            var pullRefreshApi = null;
            var id = self.getAttribute('data-pullToRefresh');
            if (!id) {
                id = ++$.uuid;
                $.data[id] = pullRefreshApi = new $.PullToRefresh(self, options);
                self.setAttribute('data-pullToRefresh', id);
            } else {
                pullRefreshApi = $.data[id];
            }
            if (options.up && options.up.auto) { //如果设置了auto，则自动上拉一次
                pullRefreshApi.pullUpLoading();
            }
            pullRefreshApis.push(pullRefreshApi);
        });
        return pullRefreshApis.length === 1 ? pullRefreshApis[0] : pullRefreshApis;
    }
})(mui, window, document);